package eval.engine.cplex;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.inject.Inject;
import eval.deploy.IDemandManager;
import eval.deploy.IDeployManager;
import eval.engine.IPlacement;
import eval.engine.IPlacementEngine;
import eval.host.Host;
import eval.host.IHostManager;
import eval.host.compare.ISortHostList;
import eval.hostEvaluate.IHostEvaluate;
import eval.utilities.configuration.ExperimentConfiguration;
import eval.vm.IVMManager;
import eval.vm.VMIdentifier;
import eval.vm.VirtualMachine;
import eval.engine.cplex.InitialCplexCode.CplexAdapter;
import eval.engine.cplex.InitialCplexCode.CplexDataHolder;
import eval.exceptions.CplexMapIndexToVmIdException;


/**
 * This class is used to get the previous tick cloud state,
 * update it as needed (e.g. remove any VM which finish it life cycle),
 * than transfer the updated status to the CPLEX code,
 * receive the new cloud status from the CPLEX module and pass the new status to the CPLEX PLACEMENT.
 * 
 * @author Yosef Moatti <moatti@il.ibm.com>
 * @author Amit Peled   <amit.kapina@gmail.com>
 * @author Igal Kaufman <igal.kaufman@gmail.com> 
 */
public class cplexEngine implements IPlacementEngine {

	/*
	 * Constants
	 * */
	static final String CPLEX_PROPERTY_PREFIX      = "";
	static final String EQUALS = " = ";
	static final String EOL = " ;\n";
	static final String REWARD_WEIGHT_NAME         = "REWARD_WEIGHT";
	static final String PLACEMENT_WEIGHT_NAME      = "PLACEMENT_WEIGHT";
	static final String RED_LINE_WEIGHT_NAME       = "RED_LINE_WEIGHT";
	static final String ENERGY_WEIGHT_NAME         = "ENERGY_WEIGHT";
	static final String CAPACITY_OVERFLOW_WEIGHT_NAME = "CAPACITY_OVERFLOW_WEIGHT";
	static final String LOAD_BALANCE_WEIGHT = "LOAD_BALANCE_WEIGHT";
	static final String USE_CPLEX_JNI = "useCplexJNI";
	// number of pieces for the piecewise approximation in the normal range.
	final static int PIECES_IN_NORMAL_RANGE = 12;
	// this is the number of pieces for the piecewise approximation in the normal range
	final static int PIECES_IN_UPPER_RANGE  = 20;
	// the number of segments for the piecewise linear function
	protected int nbPieces = PIECES_IN_NORMAL_RANGE + PIECES_IN_UPPER_RANGE;
	// default value for load relocated between two hosts to reduce the gap between them (when a VM is relocated between them)
	float relocatedLoad;
	float relocationCost = 1.0f;
	// upper bound for normal range. No relocation (of average VM or bigger, 
	// that account for relocated.load load or smaller) in the range [0, upper.bound.normal.range[
	float upperBoundNormalRange;
	// minimal utilization gap between a 100% host and another host  to trigger relocation 
	// (of average VM or smaller, that account for relocated.load or bigger load)
	float utilizationMinimalGap;
	// The square coefficient for the normal range modelization of the load cost
	Double normalRangeSquareCoef = null; 
	// the X**3 coefficient for the upper range modelization of the load cost:
	Double highRangeLevel3Coef = null;
	//if true- print the data to a file
	private static final boolean DebugMode = true;

	/*
	 * Variables
	 */
	private ExperimentConfiguration conf = ExperimentConfiguration.getSingleton();
	protected Map<Host, List<VirtualMachine>> vmMapping ;
	private long engineStartTime = 0;
	private long engineStopTime = 0;

	private IDeployManager deployManager;
	private IHostManager hostManager;
	private IVMManager vmManager;
	private IDemandManager demandManager;

	private List<Host> hosts;
	private int[] HistoryDemandArray;
	protected int[] estimatedDemandOfRejectedVms;

	private int tick;
	private int numOfHosts = 0;
	private int curNumOfVms = 0;
	private int prevNumOfVms = 0;

	private CplexMapIndexToVmId lastTickCplexMapIndexToVmId;
	private CplexMapIndexToVmId curCplexMapIndexToVmId;
	private CplexDataHolder preCplexData;
	private CplexDataHolder postCplexData;
	private CplexDataHolder lastTickCplexData;
	private int vmsToDeploy;


	/**
	 * @Constructor
	 * @param vmManager
	 * @param deployManager
	 * @param hostManager
	 * @param demandManager
	 * @param sortHostList
	 * @param hostEvaluator
	 */
	@Inject
	public cplexEngine(IVMManager vmManager, IDeployManager deployManager,
			IHostManager hostManager, IDemandManager demandManager,
			ISortHostList sortHostList,IHostEvaluate hostEvaluator) {
		this.deployManager = deployManager;
		this.hostManager = hostManager;
		this.vmManager = vmManager;
		this.demandManager = demandManager;
		this.hosts = hostManager.getHosts();
		this.numOfHosts = conf.totalNumberOfHosts;
		this.HistoryDemandArray = new int[conf.experimentTicks];
		this.estimatedDemandOfRejectedVms = new int[conf.experimentTicks];
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacementEngine#computePlacement(int, eval.engine.IPlacement)
	 */
	@Override
	public IPlacement computePlacement(int tick, IPlacement lastPlacement) {
		/* 
		 * 	Initialize placement data   
		 */
		//update start time
		engineStartTime = System.currentTimeMillis();
		//current tick experiment 
		this.tick = tick;
		String cplexInput = "";

		//CPU demand of vm's already on cloud
		Map<VirtualMachine, Integer> cpuDemandOfVMs = demandManager.getCpuDemandOfAllVMsAtTick(tick);
		//MEM demand of vm's already on cloud
		Map<VirtualMachine, Integer> memDemandOfVMs = demandManager.getMemDemandOfAllVMsAtTick(tick);
		boolean isOngoingOptimizationTick = (tick % conf.ongoingOptimizationPeriod) == 0;
		//list of vm's that going to be deploy  
		List<VirtualMachine> placedVmsAtCurTick =  new ArrayList<VirtualMachine>();
		//list of vm's that have been relocated
		List<VirtualMachine> relocatedVmsAtCurTick =  new ArrayList<VirtualMachine>();
		//list of vm's in Cloud at previous tick  
		List<VirtualMachine> vmInCloudAtPreTick =  hostManager.getAllVmAtOpenHosts();
		//list of vm's that ask to deploy but rejected.
		List<VirtualMachine> rejectedVmsAtCurTick =  new ArrayList<VirtualMachine>();

		//remove vm's list
		//List<VirtualMachine> vmsToRemoveAtCurTick = deployManager.getVMToBeRemovedAtTick(tick ,vmInCloudAtPreTick);
		List<VirtualMachine> vmsToRemoveAtCurTick = new ArrayList<VirtualMachine>();
		if(tick != 0)
			// vmsToRemoveAtCurTick = this.deployManager.getVMToBeRemovedAtTick(tick ,vmMapping);
			vmsToRemoveAtCurTick = this.deployManager.getVMToBeRemovedAtTick(tick ,hostManager.getAllVmAtOpenHosts());

		//deploy new vm's list
		List<VirtualMachine> vmsToDeployAtCurTick = deployManager.getDeploySetAtTick(tick).getVMs();
		vmsToDeploy = vmsToDeployAtCurTick.size();
		//number of vm's
		prevNumOfVms = lastPlacement.getNumberOfVMsAtCloud();
		this.curNumOfVms = prevNumOfVms - vmsToRemoveAtCurTick.size() + vmsToDeployAtCurTick.size();
		//update placement data
		if((lastTickCplexData = lastPlacement.getCplexData()) == null) 
			lastTickCplexData =  new CplexDataHolder();
		if((lastTickCplexMapIndexToVmId = lastPlacement.getCplexMapIndexToVmId())== null) 
			lastTickCplexMapIndexToVmId = new CplexMapIndexToVmId();

		/* 
		 * Pre. Placement calculation 
		 */

		//remove vm's from host
		removeVirtualMachines(vmsToRemoveAtCurTick);
		//update map from VMIdentifier to CPLEX id
		iniCurCplexMapIndexToVmId(vmsToRemoveAtCurTick,vmsToDeployAtCurTick,lastPlacement.getRejectedVMS());
		//update VM X host matrix for CPLEX  after we remove vmsToRemove 
		updateCplexData();
		this.HistoryDemandArray[tick] = getTotalCpuDemand(lastPlacement.getRejectedVMS());
		//Prepare CPLEX string data
		if(DebugMode)
			cplexInput += addDebugDataToString(cplexInput);
		cplexInput += CreateInputStringForCplex(isOngoingOptimizationTick);
		if(DebugMode)
			System.out.println(cplexInput);
		printToFileSystem(cplexInput);
		//Prepare CPLEX engine
		CplexAdapter cplexAdapter = new CplexAdapter();

		/* 
		 * Compute placement by CPLEX 
		 */
		postCplexData =  cplexAdapter.computePlacement(cplexInput, curNumOfVms, numOfHosts);


		/* 
		 * Post placement calculation  	
		 */

		//Update map for placement
		updatePlacementMap(placedVmsAtCurTick,vmsToDeployAtCurTick,
				rejectedVmsAtCurTick,vmInCloudAtPreTick,relocatedVmsAtCurTick,cpuDemandOfVMs,memDemandOfVMs);
		try {
			updateLoadOnMap(cpuDemandOfVMs,memDemandOfVMs);
		} catch (Exception e) {
			e.printStackTrace();
		}
		CplexPlacement newPlacement = new CplexPlacement(vmMapping, hostManager, lastPlacement,
				demandManager, vmsToRemoveAtCurTick, vmsToDeployAtCurTick, placedVmsAtCurTick,
				rejectedVmsAtCurTick,relocatedVmsAtCurTick, tick,postCplexData,curCplexMapIndexToVmId,calcCloudGraphGradient());
		return  newPlacement;

	}


	/* (non-Javadoc)
	 * Map part
	 */

	/*
	 * create index map between CPLEX index to VM id
	 * (Host id are fix so The CPLEX index for host are the same as Host id
	 * 
	 * @param vmsToRemove list of VMs to be remove
	 * @param vmsToDeploy list of VMs to be deploy
	 * @param rejected list of VMs that rejected at last tick
	 */
	private void iniCurCplexMapIndexToVmId(List<VirtualMachine> vmsToRemove,
			List<VirtualMachine> vmsToDeploy, List<VirtualMachine> rejected ){

		curCplexMapIndexToVmId = new CplexMapIndexToVmId(lastTickCplexMapIndexToVmId);
		if(rejected!=null &&rejected.size()>0)
			curCplexMapIndexToVmId.removeVmAtList(rejected);
		curCplexMapIndexToVmId.removeVmAtList(vmsToRemove);
		curCplexMapIndexToVmId.addVmAtList(vmsToDeploy);

	}

	/*
	 * update all change that happened at cloud,
	 * (like open hosts or removed VMs).
	 */
	private void updateCplexData(){
		preCplexData = new CplexDataHolder(lastTickCplexData,curCplexMapIndexToVmId,
				lastTickCplexMapIndexToVmId);
	}

	/*
	 * This fun. return the host that hosted specific VM at the last tick.
	 * 
	 * @param vmIndex the CPLEX id of the requested VM
	 * @return if VM was deploy at last tick - the CPLEX host id, -1 otherwise  
	 */
	private int getHostIndexAtlastPlacmentMap(int vmIndex){
		int[] it = lastTickCplexData.getPlacementMapOfVm(vmIndex);
		for(int i=0;i<it.length;i++){
			if(it[i]==1)
				return i;
		}
		return -1;
	}

	/*
	 * This fun. return the host that hosted specific VM at the current tick.
	 * 
	 * @param vmIndex the CPLEX id of the requested VM
	 * @return if VM was deploy at last tick - the CPLEX host id, -1 otherwise
	 */
	private int getHostIndexAtPostPlacmentMap(int vmIndex){
		int[] it = postCplexData.getPlacementMapOfVm(vmIndex);
		for(int i=0;i<it.length;i++){
			if(it[i]==1)
				return i;
		}
		return -1;
	}

	/*
	 * Remove VMs from cloud.
	 * 
	 * @param vmsToRemove list of VMs
	 */
	private void removeVirtualMachines(List<VirtualMachine> vmsToRemove) {
		if(!vmsToRemove.isEmpty()){
			hostManager.removeListOfVmFromHosts(vmsToRemove);
		}
	}

	/*
	 * This fun. call after the CPLEX finish is calculation, and now we need
	 * to read the data and update the our data structure (i.e. IPlacement)
	 * 
	 * @param placedVms empty list for the deployed VMs
	 * @param vmsToDeploy list with the new VMs to be deployed
	 * @param rejectedVms empty list for rejected VMs
	 * @param vmsInCloud list of all VMs at cloud (that already deployed)
	 * @param relocatedVms empty list for VMs that has been relocated
	 * @param cpuDemandOfVMs the CPU demand of all VMs at the current tick
	 * @param memDemandOfVMs the MEM demand of all VMs at the current tick
	 */
	private void updatePlacementMap(List<VirtualMachine> placedVms,List<VirtualMachine> vmsToDeploy, 
			List<VirtualMachine> rejectedVms, List<VirtualMachine> vmsInCloud,List<VirtualMachine> relocatedVms,
			Map<VirtualMachine, Integer> cpuDemandOfVMs,Map<VirtualMachine, Integer> memDemandOfVMs) {
		//Initialize map
		vmMapping = new HashMap<Host, List<VirtualMachine>>(conf.totalNumberOfHosts);
		for(Host host : hosts)
			vmMapping.put(host, new ArrayList<VirtualMachine>());
		//open hosts by CPLEX active host post map
		for(Host host : vmMapping.keySet() ){
			if(postCplexData.getValAtActiveHostMap(host.getHostId())== 1 && host.isClose()) {
				hostManager.openHost(host,tick);
			}
		}
		VirtualMachine vm = null;
		Host newHost,oldHost;
		for(int vmIndexCur = 0 ; vmIndexCur < curNumOfVms; vmIndexCur++){
			VMIdentifier vmId = null;
			int vmCplexIndexLastTick,curHostIndex,lastHostIndex;
			try {
				vmId = curCplexMapIndexToVmId.getVmIdFromCplexIndex(vmIndexCur);
				vm = vmManager.getVMbyID(vmId);
			} catch (CplexMapIndexToVmIdException e) {
				System.out.println(e.getMessage());
				e.printStackTrace();
				System.exit(1);
			}

			try {
				vmCplexIndexLastTick = lastTickCplexMapIndexToVmId.getCplexIndexFromVmId(vmId);
				//check relocation
				lastHostIndex = getHostIndexAtlastPlacmentMap(vmCplexIndexLastTick);
				curHostIndex = getHostIndexAtPostPlacmentMap(vmIndexCur);
				if(curHostIndex!=-1) {
					if(lastHostIndex!=curHostIndex) {//relocate
						newHost = hostManager.getHostByID(curHostIndex);
						oldHost = hostManager.getHostByID(lastHostIndex);
						int sumMinDemand = 0;
						int sumMemLoad = 0;
						for(VirtualMachine vmAtNewHost : hostManager.getVmsAtHost(newHost)) {
							int cpu = Math.min(cpuDemandOfVMs.get(vmAtNewHost),vmAtNewHost.getVMConfiguration().getMinCpu());
							int mem = memDemandOfVMs.get(vmAtNewHost);
							vmManager.setSatisfiedLoad(vmAtNewHost, cpu, mem);
							sumMinDemand += cpu;
							sumMemLoad += mem;
						}
						try {
							hostManager.setHostCurrentCpuLoad(newHost, sumMinDemand);
							hostManager.setHostCurrentMemLoad(newHost, sumMemLoad);
						} catch (Exception e1) {
							//we assume that CPLEX engine will reduce load before 
							//end of the tick
						} 
						hostManager.relocateVm(vm,oldHost,newHost,
								Math.min(vm.getVMConfiguration().getMinCpu(),cpuDemandOfVMs.get(vm)),
								memDemandOfVMs.get(vm));
						relocatedVms.add(vm);
						vmMapping.get(newHost).add(vm);
					} else {
						newHost = hostManager.getHostByID(curHostIndex);
						vmMapping.get(newHost).add(vm);
					}
				} else {//rejected
					rejectedVms.add(vm);
					hostManager.rejectVms(vm);
				}

			} catch (CplexMapIndexToVmIdException e) {//VM is new deploy
				curHostIndex = getHostIndexAtPostPlacmentMap(vmIndexCur);
				if(curHostIndex!=-1) {
					newHost = hostManager.getHostByID(curHostIndex);
					int vmCpuLoad = Math.min(cpuDemandOfVMs.get(vm).intValue(),vm.getVMConfiguration().getMinCpu());
					int vmMemLOad = memDemandOfVMs.get(vm).intValue();
					hostManager.addVmToHost(newHost,vm,vmCpuLoad, vmMemLOad);
					placedVms.add(vm);
					deployManager.setVmStartLife(vm, tick);
					vmMapping.get(newHost).add(vm);
					vmManager.setSatisfiedLoad(vm, vmCpuLoad, vmMemLOad);
				} else { //rejected
					rejectedVms.add(vm);
					hostManager.rejectVms(vm);
				}

			}
		}
		//close hosts
		for(Host host : vmMapping.keySet() ){
			if(postCplexData.getValAtActiveHostMap(host.getHostId()) == 0 && host.isOpen()) {
				hostManager.closeHost(host);
			}
		}
	}

	/*
	 * after we finish to update our data structure, we update the demand of the VMs 
	 * 
	 * @param cpuDemandOfVMs the CPU demand of all VMs at the current tick
	 * @param memDemandOfVMs the MEM demand of all VMs at the current tick
	 * @throws Exception 
	 */
	private void updateLoadOnMap(Map<VirtualMachine, Integer> cpuDemandOfVMs ,Map<VirtualMachine, Integer> memDemandOfVMs) throws Exception {
		//on every host at map update is VMs list load
		for(Host host : vmMapping.keySet() ) 
			updateHostLoad(host,vmMapping.get(host),cpuDemandOfVMs,memDemandOfVMs);
	}


	/*
	 *  update the load on host 
	 * 
	 * @param hostToUpdate the host to update..
	 * @param vmListToUpdate the VMs at the host
	 * @param cpuDemandOfVMs the CPU demand of all VMs at the current tick
	 * @param memDemandOfVMs the MEM demand of all VMs at the current tick
	 */
	private void updateHostLoad(Host hostToUpdate,
			List<VirtualMachine> vmListToUpdate,
			Map<VirtualMachine, Integer> cpuDemandOfVMs, 
			Map<VirtualMachine, Integer> memDemandOfVMs) {
		int sumMinDemand = 0;
		//update the reservation
		for(VirtualMachine vm : vmListToUpdate) {
			int minDEmand = Math.min(vm.getVMConfiguration().getMinCpu(),cpuDemandOfVMs.get(vm));
			vm.setSatisfiedCpuDemand(minDEmand);
			sumMinDemand += minDEmand;
		}
		try {
			//update the host data
			hostManager.setHostCurrentCpuLoad(hostToUpdate, sumMinDemand);
		} catch (Exception e1) {
			e1.printStackTrace();
			System.exit(1);
		}
		int vmNotSatisfied,tmpLoad;
		vmNotSatisfied = 0;
		//count UnSatisfied CPU demand of vm's
		for(VirtualMachine vm : vmListToUpdate) {
			tmpLoad = vm.getSatisfiedCpuDemand();
			if(cpuDemandOfVMs.get(vm).intValue()>tmpLoad)
				vmNotSatisfied++;
		}
		//give the rest of CPU demand 
		int loadToShare = hostManager.getCpuAvailableAtHost(hostToUpdate);
		int cutOfThePie;
		while (vmNotSatisfied > 0 && loadToShare >0) {
			cutOfThePie = loadToShare/vmNotSatisfied;
			if(cutOfThePie!=0) { 
				for(VirtualMachine vm : vmListToUpdate) {
					tmpLoad = cpuDemandOfVMs.get(vm).intValue()- vm.getSatisfiedCpuDemand();
					if(tmpLoad>0) {
						tmpLoad = Math.min(cutOfThePie,tmpLoad);
						vm.updateSatisfiedCpuDemand(tmpLoad);
						try {
							//update host data
							hostManager.updateHostCurrentCpuLoad(hostToUpdate,tmpLoad);
						} catch (Exception e) {
							e.printStackTrace();
						}
						loadToShare -= tmpLoad;
						if((cpuDemandOfVMs.get(vm).intValue()- vm.getSatisfiedCpuDemand())<=0)
							vmNotSatisfied--;
					}
				}
			} else {
				loadToShare =0;
			}
		}
	}


	/* (non-Javadoc)
	 * @see eval.engine.IPlacementEngine#getEngineTimeInSec()
	 */
	@Override
	public double getEngineTimeInSec() {
		return engineStopTime - engineStartTime;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacementEngine#resetEngineTime()
	 */
	@Override
	public void resetEngineTime() {
		engineStartTime = engineStopTime = 0;
	}


	/* (non-Javadoc)
	 * CPLEX part 
	 */


	/* This fun. add debug data to the CPLEX input String.
	 * 
	 * @param cplexInput the input String for the CPLEX
	 * @return the input for CPLEX after the debug data was added
	 */
	private String addDebugDataToString(String cplexInput) {
		VMIdentifier[] it = curCplexMapIndexToVmId.getCplexIndexToVmId();
		cplexInput += "\n\n////////////////new cycle////////////\n";
		cplexInput += "/////////////////////////////////////\n";
		cplexInput += "//tick = " + tick +"\n\n" ;
		cplexInput += "//Mapping of index in placement table to vmID:\n" ;
		for(int i=0; i<it.length;i++)
			cplexInput += "//" + i + " -> " + it[i] + "\n";
		cplexInput += "//Mapping of index in placement table to HostID:\n" ;
		for(int i=0; i<hosts.size();i++)
			cplexInput += "//" + i + " -> " + hosts.get(i).getHostId() + "\n";
		cplexInput += "\n\n";
		return cplexInput;
	}

	/*
	 * This fun. build the data for the outside CPLEX engine.
	 * 
	 * @param isOngoingOptimizationTick true if this tick is optimization cycle
	 * @return the complete input for CPLEX
	 */
	private String CreateInputStringForCplex(boolean isOngoingOptimizationTick){
		String inputStringForCplex = 
				"INCOME_ON_LOAD = " + conf.incomeOnLoad + ";\n" +
						"PLACEMENT_WEIGHT = " + conf.PLACEMENT_WEIGHT  + ";\n" +
						LOAD_BALANCE_WEIGHT + EQUALS + conf.RELOCATION_WEIGHT + EOL +

						"nbHosts = " + numOfHosts + ";\n" +
						"nbVM = " + curNumOfVms + ";\n" + 
						"nbResources = " + conf.nbResources + ";\n" +

						"capacity = " + getHostsCapacityArray() +";\n" +
						"reservation = " + getVMreservationArray() + ";\n" +
						"vsCpuDemand = " + getVMCpuDemandArray(tick) + ";\n" +
						"previousPlacement = " + Arrays.deepToString(preCplexData.getPlacementMap()) + ";\n" +
						"fixed = " + getFixedArray(isOngoingOptimizationTick) + ";\n" +
						"gradient = " + calcCloudGraphGradient() + ";\n" + 
						"numberOfRelocation =" + getNumberOfRelocation(isOngoingOptimizationTick) + EOL +
						"hostBaseEnergyConsumption = " + getBaseEnergyConsArray() + ";\n" +
						"hostPowerOnEnergyConsumption = " + getPowerONEngConsArray() + ";\n" +
						"inputActiveHosts = " + Arrays.toString(preCplexData.getActiveHostsMap()) /*getActiveHosts()*/ + ";\n" +
						"loadBalancingActivated = " + conf.loadBalancingActivated + EOL +
						"nbPieces = " + nbPieces + EOL +
						"loadCostSlopes = [0.9090906923468016, 2.727272077040405,4.5454534617340085, 6.363634846427611, 8.181816231121212,9.999997615814822, 11.818179000508422, 13.636360385202021,15.45454176989563, 17.272723154589222, 19.09090453928284,20.90908592397645, 100.52474004395609, 106.47300396732521,112.58968698792097, 118.87478910574212, 125.32831032078963,131.9502506330635, 138.74061004256336, 145.69938854929075,152.82658615324192, 160.12220285442078, 167.58623865282559,175.21869354845592, 183.0195675413143, 190.98886063139614,199.1265728187067, 207.4327041032418, 215.9072544850035,224.55022396399326, 233.3616125402043, 242.3414202136466] " +EOL+ 
						"loadBreakpoints = [0.04999999701976776, 0.09999999403953552,0.14999999105930328, 0.19999998807907104, 0.2499999850988388,0.29999998211860657, 0.34999997913837433, 0.3999999761581421,0.44999997317790985, 0.4999999701976776, 0.5499999672174454,0.5999999642372131, 0.6199999660253525, 0.6399999678134918,0.6599999696016312, 0.6799999713897705, 0.6999999731779099,0.7199999749660492, 0.7399999767541885, 0.7599999785423279,0.7799999803304672, 0.7999999821186066, 0.8199999839067459,0.8399999856948852, 0.8599999874830246, 0.8799999892711639,0.8999999910593033, 0.9199999928474426, 0.9399999946355819,0.9599999964237214, 0.9799999982118606] " + EOL
						;
		return inputStringForCplex;
	}

	/*
	 * Get the number of allowed relocations
	 * @param isOngoingOptimizationTick true if this tick is optimization cycle
	 * @return the number of allowed relocations
	 */
	private int getNumberOfRelocation(boolean isOngoingOptimizationTick) {
		int toRet =  vmsToDeploy;
		if(isOngoingOptimizationTick) {
			toRet += conf.numberOfRelocation;
		}
		return toRet;
	}

	/*
	 * Prepare the String of the Hosts capacity
	 * the out put  should look like: [[a1 b1 c1][a2 b2 c2]...];
	 * 	when:  
	 * 		-every block is a host
	 * 		-a is CPU host capacity
	 * 		-b is CPU host capacity
	 * 		-c is MEM host capacity
	 * 		-the block number i is the host id i
	 * 
	 * @return Hosts capacity string
	 */
	private String getHostsCapacityArray(){
		String HostCapacity = "[\n";
		for(Host host : hostManager.getHosts()){
			HostCapacity += "[" + host.getHostCapacity().getCpuCapacity() + " " + host.getHostCapacity().getCpuCapacity() + " " + host.getHostCapacity().getMemCapacity() + "]\n" ;
		}
		HostCapacity += "]";
		return HostCapacity;
	}

	/*
	 * Prepare the String of the VMs reservation
	 * the out put  should look like: [[a1 b1 c1][a2 b2 c2]...];
	 * 	when:  
	 * 		-every block is a VM
	 * 		-a always 0
	 * 		-b is VM minimum CPU 
	 * 		-c is VM minimum MEM
	 * 		-the block number i is the VM CPLEX id i  
	 * 
	 * @return VM reservation string
	 */
	private String getVMreservationArray(){
		VMIdentifier[] it = curCplexMapIndexToVmId.getCplexIndexToVmId();
		String vmReservation = "\n[";
		for(VMIdentifier vmID : it){
			VirtualMachine vm = vmManager.getVMbyID(vmID);
			vmReservation += "[0 " + vm.getVMConfiguration().getMinCpu() + " " + demandManager.getMemDemandOfVmAtTick(vm, tick) + "]\n";	
		}
		vmReservation += "]";
		return vmReservation;
	}

	/*
	 * Prepare the String of the VMs demand	
	 * the out put  should look like: [a1,a2,a3,...];
	 * 	when:  
	 * 		- a is the VM demand 
	 * 		- the place i, is the VM i (at CPLEX index system) 
	 * 
	 * @param tick the current tick
	 * @return VM demand string
	 */
	private String getVMCpuDemandArray(int tick){
		VMIdentifier[] it = curCplexMapIndexToVmId.getCplexIndexToVmId();
		String vmsCpuDemand = "\n[ ";
		for(VMIdentifier vmID : it){
			vmsCpuDemand += demandManager.getCpuDemandOfVmAtTick(vmID,tick) + ", ";
		}
		vmsCpuDemand = vmsCpuDemand.substring(0, vmsCpuDemand.length() - 2);
		vmsCpuDemand += "]";
		return vmsCpuDemand;
	}


	/*
	 * Prepare the String of the Fix,
	 * The Fix array determine if VM can be relocated or not.
	 * where: 1 VM cannot be relocated and 0 he can  	
	 * the output  should look like: [1,1,1,...0,0];
	 * 	when:  
	 * 		- the place i, is the VM i (at CPLEX index system) 
	 * 
	 * @param isOngoingOptimizationTick if this an optimization cycle
	 * @return Fix string
	 */
	private String getFixedArray(boolean isOngoingOptimizationTick) {
		String fixedArray = "\n[";
		for(int i=0; i< curNumOfVms; i++) {
			if(isOngoingOptimizationTick){
				fixedArray += "0, ";
			} else {
				try {
					lastTickCplexMapIndexToVmId.getCplexIndexFromVmId(
							curCplexMapIndexToVmId.getVmIdFromCplexIndex(i));
					fixedArray += "1 ,";
				} catch (CplexMapIndexToVmIdException e) {
					fixedArray += "0, ";
				}
			}
		}
		fixedArray = fixedArray.substring(0, fixedArray.length() - 2);
		fixedArray += "]";
		return fixedArray;
	}

	/*
	 * Prepare the String of the base energy consumption,
	 * the output  should look like: [a1,a2,a3,...];
	 * 	when:  
	 * 		- a is the cost of the host base energy consumption
	 * 		- the place i, is the Host i 
	 * 
	 * @return base energy consumption string
	 */
	private String getBaseEnergyConsArray() {
		String baseEnergyCons = "\n[";
		for(Host host :hostManager.getHosts()){	
			baseEnergyCons += host.getHostCapacity().getHostBaseEnergyConsumption() + ", ";
		}
		baseEnergyCons = baseEnergyCons.substring(0, baseEnergyCons.length() - 2);
		baseEnergyCons += "]";
		return baseEnergyCons;
	}

	/*
	 * Prepare the String of the Power ON energy consumption,
	 * the output  should look like: [a1,a2,a3,...];
	 * 	when:  
	 * 		- a is the cost of the host Power ON energy consumption
	 * 		- the place i, is the Host i 
	 * 
	 * @return Power ON energy consumption string
	 */
	private String getPowerONEngConsArray() {
		String PowerOnEnergyCons = "\n[";
		for(Host host :hostManager.getHosts()){	
			PowerOnEnergyCons += host.getHostCapacity().getHostPowerOnEnergyConsumption() + ", ";
		}
		PowerOnEnergyCons = PowerOnEnergyCons.substring(0, PowerOnEnergyCons.length() - 2);
		PowerOnEnergyCons += "]";
		return PowerOnEnergyCons;
	}

	/*
	 * return the total CPU demand include the rejected VMs.
	 * 
	 * @param rejectedVms list of the rejected VMs
	 * @return the total CPU demand
	 */
	private int getTotalCpuDemand(List<VirtualMachine> rejectedVms){
		if(tick==0)
			return 0;
		int totalDemand = 0;
		//run on deployed VMs
		for(Host host :vmMapping.keySet()) {
			for(VirtualMachine vm :vmMapping.get(host)) {
				totalDemand += demandManager.getCPUDemandOfVmAtTick(vm, tick);
			}
		}
		//run on rejected VMs
		for(VirtualMachine vm : rejectedVms){
			totalDemand += demandManager.getCPUDemandOfVmAtTick(vm, tick);
			for(int i = 1 ; i < deployManager.getEstimatedAverageLifeTime() ; i++){
				if(tick+i < conf.experimentTicks)
					estimatedDemandOfRejectedVms[tick+i] = (vm.getVMConfiguration().getMaxCpu() + vm.getVMConfiguration().getMinCpu()) / 2;
			}
		}
		totalDemand += estimatedDemandOfRejectedVms[tick];
		return totalDemand;
	}

	/*
	 * Get the gradient of CPU demand on cloud
	 *  (see function description at the developer book).
	 * 
	 * @return the gradient
	 */
	protected int calcCloudGraphGradient() {
		int historyWindowLength = conf.historyWindowLength;
		if(tick == 0)
		{return 0;}
		else if(tick == 1)
			return (HistoryDemandArray[1] - HistoryDemandArray[0]);
		int length = Math.min(historyWindowLength, tick);
		int[] historyArray;	
		int[] historyWindowArray = new int[length];
		int[] GradientsArray; 
		int[] smoothedGradientsArray; 
		for(int i=0 ; i<length; i++ )
			historyWindowArray[i] = HistoryDemandArray[tick - length + i];
		historyArray = runHoltWinterAlg(historyWindowArray);
		GradientsArray = getGradientsArray(historyArray);
		smoothedGradientsArray = runHoltWinterAlg(GradientsArray);
		return smoothedGradientsArray[smoothedGradientsArray.length -1];
	}

	/*
	 * this is the Holt-Winter algorithm for the gradient calculation
	 * algorithm function: s(t) = alfa*x(t-1) + (1-alfa)*s(t-1).
	 *  (see function description at the developer book).
	 *  
	 * @param ArrayTosmooth
	 * @return the smooth array of the demand
	 */
	private int[] runHoltWinterAlg(int[] ArrayTosmooth){
		int length = ArrayTosmooth.length;
		double alfa = conf.alfaInputForHWalgo;
		int[] smoothedArray = new int[length];
		//initial condition.
		smoothedArray[0] = ArrayTosmooth[0];
		for(int i=1; i< length ;i++ ){
			smoothedArray[i] = (int) (alfa * ArrayTosmooth[i-1] + (1-alfa) * smoothedArray[i-1]);  
		}

		return smoothedArray;
	}

	/*
	 * this is the part of the gradient calculation
	 * (see function description at the developer book).
	 * 
	 * @param inputArray
	 * @return
	 */
	private int[] getGradientsArray(int[] inputArray){
		int k = (int)(inputArray.length * conf.gradientwidth);
		int outputArray[] = new int[k];
		for(int i=0; i<k ;i++)
			outputArray[i] = (int)((inputArray[i+k] - inputArray[i]) / k);

		return outputArray;
	}

	/*
	 * print the input of the outside CPLEX engine to the file
	 * 
	 * @param CplexInput the input for CPLEX
	 */
	private void printToFileSystem(String CplexInput){
		try{
			File file = new File(conf.CplexInputFilePath);

			if(tick == 0){
				file.delete();
				file = new File(conf.CplexInputFilePath);
			}
			if (!file.exists()) 
				file.createNewFile();
			FileWriter fw = new FileWriter(file.getAbsoluteFile(),true);
			BufferedWriter bw = new BufferedWriter(fw);
			bw.write(CplexInput);
			bw.close();
		}catch (Exception e){//Catch exception if any
			System.err.println("Error: " + e.getMessage());
		}
	}

}